/*
 * Copyright (c) 2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.wso2.carbon.bpel.ode.integration;

import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.client.OperationClient;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.ServiceContext;
import org.apache.axis2.context.ServiceGroupContext;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.AxisServiceGroup;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.neethi.Policy;
import org.apache.neethi.PolicyEngine;
import org.apache.ode.bpel.epr.EndpointFactory;
import org.apache.ode.bpel.epr.MutableEndpoint;
import org.apache.ode.bpel.epr.WSAEndpoint;
import org.apache.ode.bpel.epr.WSDL11Endpoint;
import org.apache.ode.bpel.iapi.EndpointReference;
import org.apache.ode.bpel.iapi.Message;
import org.apache.ode.bpel.iapi.MessageExchange;
import org.apache.ode.bpel.iapi.PartnerRoleChannel;
import org.apache.ode.bpel.iapi.PartnerRoleMessageExchange;
import org.apache.ode.bpel.iapi.ProcessConf;
import org.apache.ode.il.OMUtils;
import org.apache.ode.utils.DOMUtils;
import org.apache.ode.utils.Namespaces;
import org.apache.ode.utils.stl.CollectionsX;
import org.apache.ode.utils.uuid.UUID;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.wso2.carbon.bpel.ode.integration.axis2.WSDLAwareSOAPProcessor;
import org.wso2.carbon.bpel.ode.integration.config.EndpointConfiguration;
import org.wso2.carbon.bpel.ode.integration.store.ProcessConfigurationImpl;
import org.wso2.carbon.bpel.ode.integration.utils.*;
import org.wso2.carbon.unifiedendpoint.core.UnifiedEndpoint;
import org.wso2.carbon.unifiedendpoint.core.UnifiedEndpointFactory;
import org.wso2.carbon.unifiedendpoint.core.UnifiedEndpointConstants;

import javax.wsdl.*;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.http.HTTPBinding;
import javax.wsdl.extensions.soap.SOAPOperation;
import javax.xml.namespace.QName;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * Implements the BPEL Process Partner Endpoint. This will handle all the communication between
 * partner services and BPEL process. This implements PartnerRoleChanel interface in-addition
 * to providing methods used to invoke external partner endpoints.
 */
public class PartnerService implements PartnerRoleChannel {
    private static final Log log = LogFactory.getLog(PartnerService.class);
    private static Log messageTraceLog = LogFactory.getLog(BPELConstants.MESSAGE_TRACE);

    // WSDL Definition for partner service
    private Definition wsdlDefinition;

    // Service QName of the partner service
    private QName serviceName;

    // Port name in the service definition of the WSDL
    private String portName;

    // Client side configuration context to use with external service invocations
    private ConfigurationContext clientConfigCtx;

    // Process configuration
    private ProcessConf processConfiguration;

    private WSAEndpoint endpointReference;

    private String endpointUrl;

    // Client side AxisConfiguration
    private AxisConfiguration axisConfig;

    // WSDL Binding to use for this service invocation
    private Binding binding;

    public PartnerService(Definition wsdlDefinition,
                          QName serviceName,
                          String portName,
                          ConfigurationContext clientConfigCtx,
                          ProcessConf pconf,
                          HttpConnectionManager connManager) throws AxisFault {
        this.wsdlDefinition = wsdlDefinition;
        this.serviceName = serviceName;
        this.portName = portName;
        this.clientConfigCtx = clientConfigCtx;
        this.processConfiguration = pconf;
        this.axisConfig = clientConfigCtx.getAxisConfiguration();

        inferBindingInformation();

        this.clientConfigCtx.setProperty(HTTPConstants.MULTITHREAD_HTTP_CONNECTION_MANAGER,
                connManager);
        this.clientConfigCtx.setProperty(HTTPConstants.REUSE_HTTP_CLIENT, "false");

        Element eprEle = BPELProcessProxy.genEPRfromWSDL(this.wsdlDefinition, this.serviceName,
                this.portName);
        if (eprEle == null) {
            throw new IllegalArgumentException("Service Port definition not found for service:"
                    + this.serviceName + " and port:" + this.portName);
        }
        this.endpointReference = EndpointFactory.convertToWSA(
                BPELProcessProxy.createServiceRef(eprEle));
        endpointUrl = endpointReference.getUrl();
    }

    public Definition getWsdlDefinition() {
        return wsdlDefinition;
    }

    public Binding getBinding() {
        return binding;
    }

    public void invoke(final PartnerRoleMessageExchange partnerRoleMessageExchange) {
        boolean isTwoWay = (partnerRoleMessageExchange.getMessageExchangePattern() ==
                MessageExchange.MessageExchangePattern.REQUEST_RESPONSE);
        try {
            // Override options are passed to the axis MessageContext so we can
            // retrieve them in our session out changeHandler.
            final MessageContext mctx = new MessageContext();


            BPELMessageContext partnerInvocationContext =
                    BPELMessageContextFactory.createBPELMessageContext(mctx, this);
            ExtensibilityElement bindingType =
                    WSDLAwareSOAPProcessor.getBindingExtension(binding);

            try {
                if (bindingType instanceof HTTPBinding) {
                    /**
                     * In the current HTTP binding support implementation we only support GET, DELETE with
                     * x-form-url-encoded and POST, PUT with application/xml content types. And as the return(out put)
                     * we only support mime cotent text/xml with part name. Example HTTP Binding definition:
                     * <wsdl:binding name="RESTServiceHttpBinding" type="ns:RESTServicePortType">
                     *     <http:binding verb="POST" />
                     *     <wsdl:operation name="modifyMobileNumber">
                     *         <http:operation location="modifyMobileNumber" />
                     *         <wsdl:input>
                     *             <mime:content type="application/x-www-form-urlencoded"/>
                     *         </wsdl:input>
                     *         <wsdl:output>
                     *             <mime:content type="text/xml" part="parameters" />
                     *         </wsdl:output>
                     *     </wsdl:operation>
                     * </wsdl:binding>
                     *
                     * *** We ignore mime:content inside input element. We infer the content type based on HTTP verb.
                     *
                     *
                     */
                    HTTPBindingHandler httpBindingHandler =
                            new HTTPBindingHandler(clientConfigCtx, serviceName, portName, wsdlDefinition);
                    HTTPBindingHandler.HTTPBindingResponse response =
                            httpBindingHandler.invoke(partnerRoleMessageExchange, partnerInvocationContext);

                    if (isTwoWay) {
                        final Operation operation = partnerRoleMessageExchange.getOperation();
                        MessageContext responseMessageContext = response.getReponseMessageContext();
                        partnerInvocationContext.setInMessageContext(responseMessageContext);
                        MessageContext fltMessageContext = response.getFaultMessageContext();
                        if (messageTraceLog.isTraceEnabled()) {
                            messageTraceLog.trace("Response message: MEXId: " +
                                    partnerRoleMessageExchange.getMessageExchangeId() +
                                    " :: " + responseMessageContext.getEnvelope());
                        }

                        if (fltMessageContext != null) {
                            replyHTTP(partnerInvocationContext, partnerRoleMessageExchange, operation,
                                    fltMessageContext, true);
                        } else {
                            replyHTTP(partnerInvocationContext, partnerRoleMessageExchange, operation,
                                    responseMessageContext, response.isFault());
                        }

                    } else {  /* one-way case */
                        partnerRoleMessageExchange.replyOneWayOk();
                    }
                } else {


                    /* make the given options the parent so it becomes the defaults of the
                    * MessageContext. That allows the user to override specific options on a given
                    * message context and not affect the overall options.
                    */
                    EndpointConfiguration endpointConf =
                            ((ProcessConfigurationImpl) processConfiguration).getEndpointConfiguration(
                                    new WSDL11Endpoint(this.serviceName, portName));

                    UnifiedEndpoint partnerEndpoint = buildUnifiedEndpoint(endpointConf);

                    SOAPUtils.createSOAPRequest(partnerInvocationContext, partnerRoleMessageExchange);


                    String mexEndpointUrl =
                            ((MutableEndpoint) partnerRoleMessageExchange.getEndpointReference())
                                    .getUrl();
                    if (!endpointUrl.equals(mexEndpointUrl)) {
                        partnerEndpoint.setAddress(mexEndpointUrl);
                    }


                    OperationClient opClient = getOperationClient(isTwoWay, mctx);
                    mctx.getOptions().setParent(opClient.getOptions());

                    /*
                     Else we assume that the epr is not changed by the process.
                     In this case there's a limitation we cannot invoke the epr in the wsdl
                     (by assingning that epr by partnerlink assign) if there is a endpoint
                     configuration available for that particular service
                    */
                    opClient.addMessageContext(mctx);
                    Options operationOptions = opClient.getOptions();

                    if (partnerEndpoint.isAddressingEnabled()) {
                        //Currently we set the action manually, but this should be handled by
                        // addressing module it-self?
                        String action = getAction(partnerRoleMessageExchange.getOperationName());
                        if (log.isDebugEnabled()) {
                            log.debug("Soap action: " + action + " found");
                        }
                        operationOptions.setAction(action);
                        //TODO set replyto as well
                        //operationOptions.setReplyTo(mctx.getReplyTo());
                    }
                    operationOptions.setTo(partnerEndpoint);


                    if (bindingType instanceof HTTPBinding) {
                        operationOptions.setProperty(Constants.Configuration.ENABLE_REST,
                                Constants.VALUE_TRUE);
                    }

                    if (messageTraceLog.isDebugEnabled()) {
                        messageTraceLog.debug("Invoking service: MEXId: " +
                                partnerRoleMessageExchange.getMessageExchangeId() +
                                " :: " + serviceName + "." +
                                partnerRoleMessageExchange.getOperationName());
                        if (messageTraceLog.isTraceEnabled()) {
                            messageTraceLog.trace("Request message: MEXId: " +
                                    partnerRoleMessageExchange.getMessageExchangeId() +
                                    " :: " +
                                    partnerInvocationContext.getInMessageContext().
                                            getEnvelope());
                        }
                    }

                    opClient.execute(true);

                    if (messageTraceLog.isDebugEnabled()) {
                        messageTraceLog.debug("Service invocation completed: MEXId: " +
                                partnerRoleMessageExchange.getMessageExchangeId() +
                                " :: " + serviceName + "." +
                                partnerRoleMessageExchange.getOperationName());
                    }

                    if (isTwoWay) {
                        final Operation operation = partnerRoleMessageExchange.getOperation();
                        MessageContext response = opClient.getMessageContext(
                                WSDLConstants.MESSAGE_LABEL_IN_VALUE);
                        partnerInvocationContext.setInMessageContext(response);
                        MessageContext flt = opClient.getMessageContext(
                                WSDLConstants.MESSAGE_LABEL_FAULT_VALUE);
                        if (messageTraceLog.isTraceEnabled()) {
                            messageTraceLog.trace("Response message: MEXId: " +
                                    partnerRoleMessageExchange.getMessageExchangeId() +
                                    " :: " + response.getEnvelope());
                        }

                        if (flt != null) {
                            reply(partnerInvocationContext, partnerRoleMessageExchange, operation,
                                    flt, true);
                        } else {
                            reply(partnerInvocationContext, partnerRoleMessageExchange, operation,
                                    response, response.isFault());
                        }

                    } else {  /* one-way case */
                        partnerRoleMessageExchange.replyOneWayOk();
                    }

                }
            } finally {
                // make sure the HTTP connection is released to the pool!
                TransportOutDescription out = mctx.getTransportOut();
                if (out != null && out.getSender() != null) {
                    out.getSender().cleanup(mctx);
                }
            }

        } catch (Throwable t) {
            String errmsg = Messages.msgErrorSendingMessageToAxisForODEMex(
                    partnerRoleMessageExchange.toString());
            log.error(errmsg, t);
            replyWithFailure(partnerRoleMessageExchange,
                    MessageExchange.FailureType.COMMUNICATION_ERROR, errmsg);
        }

    }

    private UnifiedEndpoint buildUnifiedEndpoint(EndpointConfiguration endpointConfiguration) throws AxisFault {
        UnifiedEndpointFactory uepFactory = new UnifiedEndpointFactory();
        UnifiedEndpoint partnerEndpoint;
        if (endpointConfiguration != null && (endpointConfiguration.getUnifiedEndPoint() != null ||
                endpointConfiguration.getUnifiedEndPointReference() != null)) {
            if (endpointConfiguration.getUnifiedEndPoint() != null) {
                partnerEndpoint = uepFactory.createEndpoint(endpointConfiguration.getUnifiedEndPoint());
            } else {
                String uepConfPath = endpointConfiguration.getUnifiedEndPointReference();
                if (!uepConfPath.startsWith(UnifiedEndpointConstants.VIRTUAL_GOV_REG) ||
                        !uepConfPath.startsWith(UnifiedEndpointConstants.VIRTUAL_CONF_REG) ||
                        !uepConfPath.startsWith(UnifiedEndpointConstants.VIRTUAL_REG)) {
                    if (uepConfPath.startsWith(UnifiedEndpointConstants.VIRTUAL_FILE)) {
                        uepConfPath = uepConfPath.substring(UnifiedEndpointConstants.VIRTUAL_FILE.
                                length());
                    }
                    if (isAbsoutePath(uepConfPath)) {
                        uepConfPath = UnifiedEndpointConstants.VIRTUAL_FILE + uepConfPath;
                    } else {
                        uepConfPath = getAbsolutePath(endpointConfiguration.getBasePath(), uepConfPath);
                    }
                }
                partnerEndpoint = uepFactory.createVirtualEndpoint(uepConfPath);
            }
        } else {
            partnerEndpoint = new UnifiedEndpoint();
            partnerEndpoint.setUepId(this.serviceName.getLocalPart());
            partnerEndpoint.setAddressingEnabled(true);
            partnerEndpoint.setAddressingVersion(UnifiedEndpointConstants.
                    ADDRESSING_VERSION_FINAL);
        }

        if (partnerEndpoint.getAddress() == null) {
            partnerEndpoint.setAddress(endpointUrl);
        }

        if (partnerEndpoint.isSecurityEnabled()) {
            String secPolicyKey = partnerEndpoint.getWsSecPolicyKey();
            if (secPolicyKey.startsWith(UnifiedEndpointConstants.VIRTUAL_FILE)) {
                String secPolicyLocation = secPolicyKey.substring(
                        UnifiedEndpointConstants.VIRTUAL_FILE.length());
                if (!isAbsoutePath(secPolicyLocation)) {
                    secPolicyKey = getAbsolutePath(endpointConfiguration.getBasePath(),
                            secPolicyLocation);
                } else {
                    secPolicyKey = UnifiedEndpointConstants.VIRTUAL_FILE + secPolicyLocation;
                }
                partnerEndpoint.setWsSecPolicyKey(secPolicyKey);
            }
        }

        return partnerEndpoint;
    }

    private String getAbsolutePath(String basePath, String filePath) {
        return UnifiedEndpointConstants.VIRTUAL_FILE + basePath + File.separator + filePath;
    }

    public static boolean isAbsoutePath(String filePath) {
        return filePath.startsWith("/") ||
                (filePath.length() > 1 && filePath.charAt(1) == ':');
    }

    private void inferBindingInformation() {
        Service serviceDef = wsdlDefinition.getService(serviceName);
        if (serviceDef == null) {
            throw new NullPointerException(Messages.msgServiceDefinitionNotFound(
                    serviceName.getLocalPart()));
        }
        Port port = serviceDef.getPort(portName);
        if (port == null) {
            throw new NullPointerException(Messages.msgServicePortNotFound(
                    serviceName.getLocalPart(), portName));
        }

        binding = port.getBinding();
        if (binding == null) {
            throw new NullPointerException(Messages.msgBindingNotFound(
                    serviceName.getLocalPart(), portName));
        }

    }

    private void replyWithFailure(final PartnerRoleMessageExchange odeMex,
                                  final MessageExchange.FailureType error, final String errmsg) {
        try {
            odeMex.replyWithFailure(error, errmsg, null);
        } catch (Exception e) {
            String emsg = "Error executing replyWithFailure; reply will be lost.";
            log.error(emsg, e);
        }
    }

    private void reply(final BPELMessageContext partnerInvocationContext,
                       final PartnerRoleMessageExchange odeMex, final Operation operation,
                       final MessageContext reply, final boolean isFault) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("Received response for MEX " + odeMex);
            }
            if (isFault) {
                Document odeMsg = DOMUtils.newDocument();
                Element odeMsgEl = odeMsg.createElementNS(null, "message");
                odeMsg.appendChild(odeMsgEl);
                Fault fault = SOAPUtils.parseSoapFault(odeMsgEl, reply.getEnvelope(), operation);

                if (fault != null) {
                    if (log.isWarnEnabled()) {
                        log.warn("Fault response: faultName=" + fault.getName() + " faultType="
                                + fault.getMessage().getQName() + "\n"
                                + DOMUtils.domToString(odeMsgEl));
                    }

                    QName faultType = fault.getMessage().getQName();
                    QName faultName = new QName(wsdlDefinition.getTargetNamespace(),
                            fault.getName());
                    Message response = odeMex.createMessage(faultType);
                    response.setMessage(odeMsgEl);

                    odeMex.replyWithFault(faultName, response);
                } else {
                    if (log.isWarnEnabled()) {
                        log.warn("Fault response: faultType=(unkown)\n"
                                + reply.getEnvelope().toString());
                    }
                    odeMex.replyWithFailure(MessageExchange.FailureType.OTHER,
                            reply.getEnvelope().getBody().getFault().getText(),
                            OMUtils.toDOM(reply.getEnvelope().getBody()));
                }
            } else {
                Message response =
                        SOAPUtils.parseSOAPResponseFromPartner(partnerInvocationContext, odeMex);
                if (log.isDebugEnabled()) {
                    log.debug("Response:\n" + (response.getMessage() != null ?
                            DOMUtils.domToString(response.getMessage()) : "empty"));
                }
                odeMex.reply(response);
            }
        } catch (Exception ex) {
            String errmsg = "Unable to process response: " + ex.getMessage();
            log.error(errmsg, ex);
            odeMex.replyWithFailure(MessageExchange.FailureType.OTHER, errmsg, null);
        }
    }

    private void replyHTTP(final BPELMessageContext partnerInvocationContext,
                           final PartnerRoleMessageExchange odeMex, final Operation operation,
                           final MessageContext reply, final boolean isFault) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("Received response for MEX " + odeMex);
            }
            if (isFault) {
                Document odeMsg = DOMUtils.newDocument();
                Element odeMsgEl = odeMsg.createElementNS(null, "message");
                odeMsg.appendChild(odeMsgEl);

                QName faultType = new QName("http://wso2.org/bps/fault", "HTTPBindingFault");
                QName faultName = new QName("http://wso2.org/bps/fault", "RESTPartnerServiceError");

                Element fault = odeMsg.createElementNS(null, "fault");
                fault.setTextContent("Error returned from REST Partner");
                odeMsgEl.appendChild(fault);

                Message response = odeMex.createMessage(faultType);
                response.setMessage(odeMsgEl);

                odeMex.replyWithFault(faultName, response);
            } else {
                Message response =
                        SOAPUtils.parseResponseFromRESTService(partnerInvocationContext, odeMex);
                if (log.isDebugEnabled()) {
                    log.debug("Response:\n" + (response.getMessage() != null ?
                            DOMUtils.domToString(response.getMessage()) : "empty"));
                }
                odeMex.reply(response);
            }
        } catch (Exception ex) {
            String errmsg = "Unable to process response: " + ex.getMessage();
            log.error(errmsg, ex);
            odeMex.replyWithFailure(MessageExchange.FailureType.OTHER, errmsg, null);
        }

    }



    /**
     * Extracts endpoint information from ODE message exchange to stuff them into Axis MessageContext.
     */
    private void populateWSAddressingOptions(MessageContext ctxt,
                                             PartnerRoleMessageExchange odeMex) {
        WSAEndpoint targetWSAEPR = EndpointFactory.convertToWSA(
                (MutableEndpoint) odeMex.getEndpointReference());
        WSAEndpoint myRoleWSAEPR = EndpointFactory.convertToWSA(
                (MutableEndpoint) odeMex.getMyRoleEndpointReference());
        WSAEndpoint targetEPR = new WSAEndpoint(targetWSAEPR);

        String partnerSessionId = odeMex.getProperty(
                MessageExchange.PROPERTY_SEP_PARTNERROLE_SESSIONID);
        String myRoleSessionId = odeMex.getProperty(MessageExchange.PROPERTY_SEP_MYROLE_SESSIONID);

        if (partnerSessionId != null) {
            if (log.isDebugEnabled()) {
                log.debug("Partner session identifier found for WSA endpoint: " + partnerSessionId);
            }
            targetEPR.setSessionId(partnerSessionId);
        }
        //options.setProperty(ODEService.TARGET_SESSION_ENDPOINT, targetEPR);

        if (myRoleWSAEPR != null) {    //Asynchronous call??
            WSAEndpoint myRoleEPR = new WSAEndpoint(myRoleWSAEPR);
            if (myRoleSessionId != null) {
                if (log.isDebugEnabled()) {
                    log.debug("MyRole session identifier found for myrole (callback) WSA endpoint: "
                            + myRoleSessionId);
                }
                myRoleEPR.setSessionId(myRoleSessionId);
            }
            //options.setProperty(ODEService.CALLBACK_SESSION_ENDPOINT, myRoleEPR);
            org.apache.axis2.addressing.EndpointReference replyToEP =
                    new org.apache.axis2.addressing.EndpointReference();
            replyToEP.setAddress(myRoleEPR.getUrl());
            ctxt.setReplyTo(replyToEP);
        } else {
            if (log.isDebugEnabled()) {
                log.debug("My-Role EPR not specified, SEP will not be used.");
            }
        }

        String action = getAction(odeMex.getOperationName());
        ctxt.setSoapAction(action);

        if (MessageExchange.MessageExchangePattern.REQUEST_RESPONSE
                == odeMex.getMessageExchangePattern()) {
            org.apache.axis2.addressing.EndpointReference annonEpr =
                    new org.apache.axis2.addressing.EndpointReference(Namespaces.WS_ADDRESSING_ANON_URI);
            ctxt.setReplyTo(annonEpr);
            ctxt.setMessageID("uuid:" + new UUID().toString());
        }
    }


    private OperationClient getOperationClient(boolean isTwoWay, MessageContext msgCtx)
            throws AxisFault {
        AxisService anonymousService = AnonymousServiceFactory.getAnonymousService(serviceName,
                portName,
                axisConfig);
        anonymousService.engageModule(axisConfig.getModule("UEPModule"));
        anonymousService.getParent().addParameter(
                BPELConstants.HIDDEN_SERVICE_PARAM, "true");
        ServiceGroupContext sgc = new ServiceGroupContext(
                clientConfigCtx, (AxisServiceGroup) anonymousService.getParent());
        ServiceContext serviceCtx = sgc.getServiceContext(anonymousService);

        // get a reference to the DYNAMIC operation of the Anonymous Axis2 service
        AxisOperation axisAnonymousOperation = anonymousService.getOperation(
                isTwoWay ? ServiceClient.ANON_OUT_IN_OP : ServiceClient.ANON_OUT_ONLY_OP);

        Options clientOptions = cloneOptions(msgCtx.getOptions());
        clientOptions.setExceptionToBeThrownOnSOAPFault(false);
        /* This value doesn't overrideend point config. */
        clientOptions.setTimeOutInMilliSeconds(60000);

        return axisAnonymousOperation.createClient(serviceCtx, clientOptions);
    }

    private Policy loadPolicy(String fileName, String basePath) {
        Policy policyDoc = null;
        FileLoadingUtil fileLoader = new FileLoadingUtil(basePath);
        if (log.isDebugEnabled()) {
            log.debug("Applying security policy: " + fileName);
        }
        try {
            InputStream policyStream = fileLoader.load(fileName);
            try {
                policyDoc = PolicyEngine.getPolicy(policyStream);
            } finally {
                policyStream.close();
            }
        } catch (IOException e) {
            String errMsg = "Exception while parsing policy: " + fileName;
            log.error(errMsg, e);
            throw new IllegalArgumentException("Exception while parsing policy: " + fileName, e);
        } catch (FileLoadingUtilException e) {
            String errMsg = "File Loading Exception";
            log.error(errMsg, e);
            throw new IllegalArgumentException(errMsg, e);
        }

        return policyDoc;
    }

    /**
     * Extracts the action to be used for the given operation.  It first checks to see
     * if a value is specified using WS-Addressing in the portType, it then falls back onto
     * getting it from the SOAP Binding.
     *
     * @param operation the name of the operation to get the Action for
     * @return The action value for the specified operation
     */
    private String getAction(String operation) {
        String action = getWSAInputAction(operation);
        if (action == null || "".equals(action)) {
            action = getSoapAction(operation);
        }
        return action;
    }

    /**
     * Attempts to extract the WS-Addressing "Action" attribute value from the operation definition.
     * When WS-Addressing is being used by a service provider, the "Action" is specified in the
     * portType->operation instead of the SOAP binding->operation.
     *
     * @param operation The name of the operation to extract the SOAP Action from
     * @return the SOAPAction value if one is specified, otherwise empty string
     */
    public String getWSAInputAction(String operation) {
        BindingOperation bop = binding.getBindingOperation(operation, null, null);
        if (bop == null) {
            return "";
        }

        Input input = bop.getOperation().getInput();
        if (input != null) {
            Object actionQName = input.getExtensionAttribute(new QName(Namespaces.WS_ADDRESSING_NS,
                    "Action"));
            if (actionQName != null && actionQName instanceof QName) {
                return ((QName) actionQName).getLocalPart();
            }
        }
        return "";
    }

    /**
     * Attempts to extract the SOAP Action is defined in the WSDL document.
     *
     * @param operation The name of the operation to extract the SOAP Action from
     * @return the SOAPAction value if one is specified, otherwise empty string
     */
    public String getSoapAction(String operation) {
        BindingOperation bop = binding.getBindingOperation(operation, null, null);
        if (bop == null) {
            return "";
        }

        for (SOAPOperation soapOp : CollectionsX.filter(bop.getExtensibilityElements(),
                SOAPOperation.class)) {
            return soapOp.getSoapActionURI();
        }

        return "";
    }

    /**
     * Clones the given {@link org.apache.axis2.client.Options} object. This is not a deep copy
     * because this will be called for each and every message going out from synapse. The parent
     * of the cloning options object is kept as a reference.
     *
     * @param options clonning object
     * @return clonned Options object
     */
    public static Options cloneOptions(Options options) {

        // create new options object and set the parent
        Options clonedOptions = new Options(options.getParent());

        // copy general options
        clonedOptions.setCallTransportCleanup(options.isCallTransportCleanup());
        clonedOptions.setExceptionToBeThrownOnSOAPFault(options.isExceptionToBeThrownOnSOAPFault());
        clonedOptions.setManageSession(options.isManageSession());
        clonedOptions.setSoapVersionURI(options.getSoapVersionURI());
        clonedOptions.setTimeOutInMilliSeconds(options.getTimeOutInMilliSeconds());
        clonedOptions.setUseSeparateListener(options.isUseSeparateListener());

        // copy transport related options
        clonedOptions.setListener(options.getListener());
        clonedOptions.setTransportIn(options.getTransportIn());
        clonedOptions.setTransportInProtocol(options.getTransportInProtocol());
        clonedOptions.setTransportOut(clonedOptions.getTransportOut());

        // copy username and password options
        clonedOptions.setUserName(options.getUserName());
        clonedOptions.setPassword(options.getPassword());

        // cloen the property set of the current options object
        for (Object o : options.getProperties().keySet()) {
            String key = (String) o;
            clonedOptions.setProperty(key, options.getProperty(key));
        }

        return clonedOptions;
    }

    public EndpointReference getInitialEndpointReference() {
        return endpointReference;
    }

    public void close() {
        //Don't we need to implement this method?? pls comment
    }
}